/* ----------------------------------------------------------------------------
   The Kiwi Toolkit - A Java Class Library
   Copyright (C) 1998-2008 Mark A. Lindner

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this library; if not, see <http://www.gnu.org/licenses/>.
   ----------------------------------------------------------------------------
*/

package com.hyperrealm.kiwi.ui.dialog;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import javax.swing.border.EmptyBorder;

import com.hyperrealm.kiwi.ui.*;
import com.hyperrealm.kiwi.util.*;
import com.hyperrealm.kiwi.event.*;

/** A base class for custom dialog windows. This class provides some base
 * functionality that can be useful across many different types of dialogs.
 * The class constructs a skeleton dialog consisting of an optional comment
 * line, an icon, <i>OK</i> and <i>Cancel</i> buttons, and a middle area that
 * must be filled in by subclassers.
 * <p>
 * A <code>ComponentDialog</code> is <i>accepted</i> by clicking the
 * <i>OK</i> button, though subclassers can determine the conditions under
 * which a dialog may be accepted by overriding the <code>accept()</code>
 * method; it is <i>cancelled</i> by clicking the <i>Cancel</i> button or
 * closing the window.
 *
 * @author Mark Lindner
 */

public abstract class ComponentDialog extends KDialog
{
  /** The OK button. */
  protected KButton b_ok;

  /** The Cancel button. */
  protected KButton b_cancel = null;
  
  private _ActionListener actionListener;
  private KPanel main;
  private KLabel iconLabel, commentLabel;
  private JTextField inputComponent = null;
  private ButtonPanel buttons;
  private int fixedButtons = 1;

  /** Construct a new <code>ComponentDialog</code>.
   *
   * @param parent The parent dialog for this dialog.
   * @param title The title for this dialog's window.
   * @param modal A flag specifying whether this dialog will be modal.
   */
  
  public ComponentDialog(Dialog parent, String title, boolean modal)
  {
    this(parent, title, modal, true);
  }

  /** Construct a new <code>ComponentDialog</code>.
   *
   * @param parent The parent dialog for this dialog.
   * @param title The title for this dialog's window.
   * @param modal A flag specifying whether this dialog will be modal.
   * @param hasCancel A flag specifying whether this dialog should have a
   * <i>Cancel</i> button.
   */
  
  public ComponentDialog(Dialog parent, String title, boolean modal,
                         boolean hasCancel)
  {
    super(parent, title, modal);

    _init(hasCancel);
  }
  
  /** Construct a new <code>ComponentDialog</code>.
   *
   * @param parent The parent frame for this dialog.
   * @param title The title for this dialog's window.
   * @param modal A flag specifying whether this dialog will be modal.
   */

  public ComponentDialog(Frame parent, String title, boolean modal)
  {
    this(parent, title, modal, true);
  }

  /** Construct a new <code>ComponentDialog</code>.
   *
   * @param parent The parent frame for this dialog.
   * @param title The title for this dialog's window.
   * @param modal A flag specifying whether this dialog will be modal.
   * @param hasCancel A flag specifying whether this dialog should have a
   * <i>Cancel</i> button.
   */

  public ComponentDialog(Frame parent, String title, boolean modal,
                         boolean hasCancel)
  {
    super(parent, title, modal);

    _init(hasCancel);
  }

  /*
   * Common initialization.
   */

  private void _init(boolean hasCancel)
  {
    LocaleData loc = LocaleManager.getDefault().getLocaleData("KiwiDialogs");
  
    actionListener = new _ActionListener();
    
    main = getMainContainer();
    main.setBorder(new EmptyBorder(5, 5, 5, 5));
    main.setLayout(new BorderLayout(5, 5));

    commentLabel = new KLabel(loc.getMessage("kiwi.dialog.prompt"));
    main.add("North", commentLabel);

    iconLabel = new KLabel();
    iconLabel.setBorder(new EmptyBorder(5, 0, 5, 0));
    iconLabel.setVerticalAlignment(SwingConstants.CENTER);
    iconLabel.setHorizontalAlignment(SwingConstants.CENTER);

    buttons = new ButtonPanel();

    b_ok = new KButton(loc.getMessage("kiwi.button.ok"));
    b_ok.addActionListener(actionListener);
    buttons.addButton(b_ok);

    if(hasCancel)
    {
      b_cancel = new KButton(loc.getMessage("kiwi.button.cancel"));
      b_cancel.addActionListener(actionListener);
      buttons.addButton(b_cancel);
      fixedButtons++;
    }

    main.add("South", buttons);

    installDialogUI();
  }

  /** Install the component returned by <code>buildDialogUI()</code>
   * into the dialog. If it is not possible to build the dialog UI at
   * the time that the constructor calls <code>buildDialogUI()</code>,
   * that method should return <b>null</b>. The subclass constructor
   * can then explicitly call <code>installDialogUI()</code>, at which
   * time <code>buildDialogUI</code> should return a valid component.
   */

  protected void installDialogUI()
  {
    Component c = buildDialogUI();
    if(c != null)
    {
      main.add("Center", c);
      pack();
    }
  }

  /** Set the vertical position of the icon.
   *
   * @param position The vertical position: one of
   <code>SwingConstants.TOP</code>,
   * <code>SwingConstants.BOTTOM</code>, or
   * <code>SwingConstants.CENTER</code>.
   *
   * @since Kiwi 2.1
   */
  
  public void setIconPosition(int position)
  {
    iconLabel.setVerticalAlignment(position);
  }
  
  /** Construct the component that will be displayed in the center of the
   * dialog window. Subclassers implement this method to customize the look of
   * the dialog.
   *
   * @return A <code>Component</code> to display in the dialog, or
   * <code>null</code> if the component will be constructed later.
   */
  
  protected abstract Component buildDialogUI();

  /** Show or hide the dialog.
   */
  
  public void setVisible(boolean flag)
  {
    if(flag)
    {
      validate();
      //pack();
    }
    super.setVisible(flag);
  }

  /** Register a text field with this dialog. In some dialogs, most notably
   * <code>KInputDialog</code>, pressing <i>Return</i> in a text field is
   * equivalent to pressing the dialog's <i>OK</i> button. Subclassers may use
   * this method to register a text field that should function in this way.
   *
   * @param c The <code>JTextField</code> to register.
   */

  protected void registerTextInputComponent(JTextField c)
  {
    inputComponent = c;

    inputComponent.addActionListener(actionListener);
  }

  /** This is overridden to fix a Swing layout bug. */

  /*
    public void pack()
    {
    // only do this for JDK 1.3 and earlier

    // add a test here
    
    boolean old = isResizable();
    setResizable(true);
    super.pack();
    setResizable(old);
    }
  */
  
  /** Change the dialog's comment.
   *
   * @param comment The new text to display in the comment portion of the
   * dialog.
   */

  public void setComment(String comment)
  {
    setComment(null, comment);
  }

  /** Change the dialog's comment.
   *
   * @param comment The new text to display in the comment portion of the
   * dialog.
   * @param icon An icon to display to the left of the comment text.
   *
   * @since Kiwi 2.0
   */

  public void setComment(Icon icon, String comment)
  {
    commentLabel.setText(comment);
    commentLabel.setIcon(icon);
    commentLabel.invalidate();
    commentLabel.validate();
  }

  /** Set the icon to display in the left part of the dialog
   * window. The method can be called by classes that extend this
   * class to provide an appropriate icon for the dialog.
   *
   * @param icon The icon.
   */

  public void setIcon(Icon icon)
  {
    if(icon != null)
    {
      iconLabel.setIcon(icon);
      main.add("West", iconLabel);
    }
    else
      main.remove(iconLabel);
  }

  /* action listener */

  private class _ActionListener implements ActionListener
  {
    public void actionPerformed(ActionEvent evt)
    {
      Object o = evt.getSource();

      if((o == b_ok) || (o == inputComponent))
      {
        doAccept();
      }
      else if(o == b_cancel)
      {
        if(canCancel())
          doCancel();
      }
    }
  }

  /** Determine if the dialog can be cancelled. This method is called in
   * response to a click on the <i>Cancel</i> button or on the dialog
   * window's close icon/option. Subclassers may wish to override this
   * method to prevent cancellation of a window in certain circumstances.
   *
   * @return <code>true</code> if the dialog may be cancelled,
   * <code>false</code> otherwise. The default implementation returns
   * <code>true</code> if the dialog has a <i>Cancel</i> button and
   * <code>false</code> otherwise.
   */
  
  protected boolean canCancel()
  {
    return(b_cancel != null);
  }
  
  /* Set the opacity on all the buttons */

  void setButtonOpacity(boolean flag)
  {
    Component c[] = buttons.getComponents();

    for(int i = 0; i < c.length; i++)
    {
      if(c[i] instanceof JButton)
        ((JButton)c[i]).setOpaque(flag);
    }
  }

  /** Add a button to the dialog's button panel. The button is added
   * immediately before the <i>OK</i> button.
   *
   * @param button The button to add.
   */
  
  protected void addButton(JButton button)
  {
    addButton(button, -1);
  }

  /** Add a button to the dialog's button panel at the specified position.
   *
   * @param button The button to add.
   * @param pos The position at which to add the button. A value of 0 denotes
   * the first position, and -1 denotes the last position. The possible
   * range of values for <code>pos</code> excludes the <i>OK</i> and
   * (if present) <i>Cancel</i> buttons; buttons may not be added after
   * these "fixed" buttons.
   */
  
  protected void addButton(JButton button, int pos)
    throws IllegalArgumentException
  {
    int bc = buttons.getButtonCount();
    int maxpos = bc - fixedButtons;

    if(pos > maxpos)
      throw(new IllegalArgumentException("Position out of range."));      
    else if(pos < 0)
      pos = maxpos;

    buttons.addButton(button, pos);
  }

  /** Remove a button from the dialog's button panel.
   *
   * @param button The button to remove. Neither the <i>OK</i> nor the
   * <i>Cancel</i> button may be removed.
   */
  
  protected void removeButton(JButton button)
  {
    if((button != b_ok) && (button != b_cancel))
      buttons.removeButton(button);
  }

  /** Remove a button from the specified position in the  dialog's button
   * panel.
   *
   * @param pos The position of the button to remove, where 0 denotes
   * the first position, and -1 denotes the last position. The possible
   * range of values for <code>pos</code> excludes the <i>OK</i> and
   * (if present) <i>Cancel</i> buttons; these "fixed" buttons may not be
   * removed.
   */
  
  protected void removeButton(int pos)
  {
    int bc = buttons.getButtonCount();
    int maxpos = bc - fixedButtons;

    if(pos > maxpos)
      throw(new IllegalArgumentException("Position out of range."));      
    else if(pos < 0)
      pos = maxpos;

    buttons.removeButton(pos);
  }

  /** Set the label text for the <i>OK</i> button.
   *
   * @param text The text for the accept button (for example, "Yes").
   */

  public void setAcceptButtonText(String text)
  {
    b_ok.setText(text);
  }

  /** Set the label text for the <i>Cancel</i> button.
   *
   * @param text The text for the cancel button (for example, "No").
   */
  
  public void setCancelButtonText(String text)
  {
    if(b_cancel != null)
      b_cancel.setText(text);
  }

}

/* end of source file */
